using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MKDS_Course_Editor.GCN.BMD
{
    class SHP1
    {
        public class Shp1Header
        {
            public string tag;
            public UInt32 sizeOfSection;
            public UInt16 batchCount; //number of batches
            public UInt16 pad; //??
            public UInt32 offsetToBatches; //should be 0x2c (batch info starts here)

            public UInt32 offsetUnknown; //??
            public UInt32 zero; //??
            public UInt32 offsetToBatchAttribs;  //batch vertex attrib start

            //The matrixTable is an array of UInt16, which maps from the matrix data indices
            //to Drw1Data arrays indices. If a batch contains multiple packets, for the
            //2nd, 3rd, ... packet this array may contain 0xffff values, which means that
            //the corresponding index from the previous packet should be used.
            public UInt32 offsetToMatrixTable;


            public UInt32 offsetData; //start of the actual primitive data
            public UInt32 offsetToMatrixData;
            public UInt32 offsetToPacketLocations; //offset to packet start/length info

            //(all offsets relative to Shp1Header start)
        };

        //Shp1Header.batchCount many of these structs are
        //stored at Shp1Header.offsetToBatches
        public class Batch
        {
            //public byte matrixType; //0, 1, 2, 3 "matrix type" according to yazor
            //0 seems to be the default value

            //1 could mean y-billboard (mo_fire02.bmd, default.bmd)
            //or billboard (geostar.bmd)
            //RESOLVED: I just checked in sunshine, default.bmd always
            //faces the viewer, so this means billboard
            //(camera plane billboards, not camera centered billboards)

            //2 is probably y-billboard (see snowman1.bmd), looks like
            //that in mario kart in-game as well

            //3 is about as frequent as 1, but i have no idea what this
            //could mean (don't inherit parent transform?) (hum, yaz0r's
            //code suggests that this means multimatrix transform...
            //but that would conlict with my understanding of DRW1 and EVP1...)
            //-> probably means multimatrix

            public byte unknown2; //seems to be always 0xff
            public UInt16 packetCount; //number of packets belonging to this batch

            //attribs used for the strips in this batch. relative to
            //Shp1Header.offsetToBatchAttribs
            //Read StripTypes until you encounter an 0x000000ff/0x00000000,
            //for all these types indices are included. If, for example,
            //a Batch has types (9, 3), (a, 3), (0xff, 0), then for this batch two shorts (= 3)
            //are stored per vertex: position index and normal index
            public UInt16 offsetToAttribs;

            public UInt16 firstMatrixData; //index to first matrix data (packetCount consecutive indices)
            public UInt16 firstPacketLocation; //index to first packet location (packetCount consecutive indices)


            public UInt16 unknown3; //0xffff

            public float unknown4;

            //some kind of bounding box (in which coord frame?)
            public Attributes attribs = new Attributes();
            public List<Packet> packets = new List<Packet>();

            public float[] bbMin = new float[3], bbMax = new float[3]; //experimental
            public byte matrixType; //experimental
        }

        public class BatchAttrib
        {
            public UInt32 attrib; //cf. ArrayFormat.arrayType
            public UInt32 dataType; //cf. ArrayFormat.dataType (always bytes or shorts...)
        }

        //for every packet a PacketLocation struct is stored at
        //Shp1Header.offsetToPacketLocation + Batch.firstPacketLocation*sizeof(PacketLocation).
        //This struct stores where the primitive data for this packet is stored in the
        //data block.
        public class PacketLocation
        {
            public UInt32 size; //size in bytes of packet
            public UInt32 offset; //relative to Shp1Header.offsetData
        }

        public class Primitive
        {
            public byte primitiveType; //see above
            public UInt16 numVertices; //that many vertices included in this primitive - for
            //each vertex indices are stored according to batch type
            public byte type;
            public List<Index> points = new List<Index>();
        }

        //for every packet a MatrixData struct is stored at
        //Shp1Header.offsetToMatrixData + Batch.firstMatrixData*sizeof(MatrixData).
        //This struct stores which part of the MatrixTable belongs to this packet
        //(the matrix table is stored at Shp1Header.offsetToMatrixTable)
        public class MatrixData //from yaz0r's source (animation stuff)
        {
            public UInt16 unknown1;
            public UInt16 count; //count many consecutive indices into matrixTable
            public UInt32 firstIndex; //first index into matrix table
        }


        class BatchAttribs : List<BatchAttrib> { }
        public class Index
        {
            public UInt16 matrixIndex;
            public UInt16 posIndex;
            public UInt16 normalIndex;
            public UInt16[] colorIndex = new ushort[2];
            public UInt16[] texCoordIndex = new ushort[8];
        }

        /*enum
        {
          GX_TRIANGLE_STRIP = 0x98,
          GX_TRIANGLE_FAN = 0xa0
        };*/

        public class Packet
        {
            public List<Primitive> primitives = new List<Primitive>();

            public List<UInt16> matrixTable = new List<ushort>(); //maps attribute matrix index to draw array index
        }

        public class Attributes
        {
            public bool hasMatrixIndices, hasPositions, hasNormals;
            public bool[] hasColors = new bool[2];
            public bool[] hasTexCoords = new bool[8];
        }

        public class Shp1
        {
            public List<Batch> batches = new List<Batch>();

            //TODO: unknown data is missing, ...
        };


        private static BatchAttribs getBatchAttribs(EndianBinaryReader er, int off)
        {
            int old = (int)er.BaseStream.Position;

            er.BaseStream.Position = off;

            BatchAttribs ret = new BatchAttribs();

            BatchAttrib attrib = new BatchAttrib();
            attrib.attrib = er.ReadUInt32();
            attrib.dataType = er.ReadUInt32();

            while (attrib.attrib != 0xff)
            {
                ret.Add(attrib);

                attrib.attrib = er.ReadUInt32();
                attrib.dataType = er.ReadUInt32();
            }

            er.BaseStream.Position = old;
            return ret;
        }

        private static void dumpPacketPrimitives(BatchAttribs attribs, int dataSize, EndianBinaryReader er, Packet dst)
        {
            bool done = false;
            int readBytes = 0;

            while (!done)
            {
                byte type = er.ReadByte();
                ++readBytes;

                if (type == 0 || readBytes >= dataSize)
                {
                    done = true;
                    continue;
                }

                dst.primitives.Add(new Primitive());
                Primitive currPrimitive = dst.primitives.Last();
                currPrimitive.type = type;

                UInt16 count = er.ReadUInt16();
                readBytes += 2;

                currPrimitive.points.AddRange(new Index[count]);

                for (int j = 0; j < count; ++j)
                {
                    Index currPoint = currPrimitive.points[j];

                    for (int k = 0; k < attribs.Count; ++k)
                    {
                        UInt16 val = new ushort();

                        //get value
                        switch (attribs[k].dataType)
                        {
                            case 1: //s8
                                {
                                    byte tmp;
                                    tmp = er.ReadByte();
                                    val = tmp;
                                    readBytes += 1;
                                } break;

                            case 3: //s16
                                {
                                    val = er.ReadUInt16();
                                    readBytes += 2;
                                } break;

                            default:
                                {
                                    //assert(false && "shp1: got invalid data type in packet. should never happen because "
                                    //"dumpBatch() should check this before calling dumpPacket()");
                                    break;
                                }
                        }

                        //set appropriate index
                        switch (attribs[k].attrib)
                        {
                            case 0:
                                currPoint.matrixIndex = val;
                                break;

                            case 9:
                                currPoint.posIndex = val;
                                break;

                            case 0xa:
                                currPoint.normalIndex = val;
                                break;

                            case 0xb:
                            case 0xc:
                                currPoint.colorIndex[attribs[k].attrib - 0xb] = val;
                                break;

                            case 0xd:
                            case 0xe:
                            case 0xf:
                            case 0x10:
                            case 0x11:
                            case 0x12:
                            case 0x13:
                            case 0x14:
                                currPoint.texCoordIndex[attribs[k].attrib - 0xd] = val;
                                break;

                            default:
                                {
                                    //assert(false && "shp1: got invalid attrib in packet. should never happen because "
                                    //"dumpBatch() should check this before calling dumpPacket()");

                                    break; //ignore unknown types, it's enough to warn() in dumpBatch
                                }
                        }
                    }
                }
            }
        }

        private static void dumpBatch(Batch batch, Shp1Header h, EndianBinaryReader er, long baseOffset, Batch dst)
        {
            int i;

            dst.bbMin = batch.bbMin;//new float[]{batch.bbMin[0], batch.bbMin[1], batch.bbMin[2]};
            dst.bbMax = batch.bbMax;//.setXYZ(batch.bbMax[0], batch.bbMax[1], batch.bbMax[2]);
            dst.matrixType = batch.matrixType;

            //read and interpret batch vertex attribs
            BatchAttribs attribs = getBatchAttribs(er,
              (int)baseOffset + (int)h.offsetToBatchAttribs + (int)batch.offsetToAttribs);

            dst.attribs.hasMatrixIndices = dst.attribs.hasPositions = dst.attribs.hasNormals = false;
            for (i = 0; i < 2; ++i) dst.attribs.hasColors[i] = false;
            for (i = 0; i < 8; ++i) dst.attribs.hasTexCoords[i] = false;
            for (i = 0; i < attribs.Count; ++i)
            {
                if (attribs[i].dataType != 1 && attribs[i].dataType != 3)
                {
                    // warn("shp1, dumpBatch(): unknown attrib data type %d, skipping batch", attribs[i].dataType);
                    return;
                }

                switch (attribs[i].attrib)
                {
                    case 0:
                        dst.attribs.hasMatrixIndices = true;
                        break;

                    case 9:
                        dst.attribs.hasPositions = true;
                        break;

                    case 0xa:
                        dst.attribs.hasNormals = true;
                        break;

                    case 0xb:
                    case 0xc:
                        dst.attribs.hasColors[attribs[i].attrib - 0xb] = true;
                        break;

                    case 0xd:
                    case 0xe:
                    case 0xf:
                    case 0x10:
                    case 0x11:
                    case 0x12:
                    case 0x13:
                    case 0x14:
                        dst.attribs.hasTexCoords[attribs[i].attrib - 0xd] = true;
                        break;

                    default:
                        //warn("shp1, dumpBatch(): unknown attrib %d in batch, it might not display correctly", attribs[i].attrib);
                        //return; //it's enough to warn
                        break;
                }
            }

            //read packets
            dst.packets.AddRange(new Packet[batch.packetCount]);
            for (i = 0; i < batch.packetCount; ++i)
            {
                //read packet location for current packet
                PacketLocation packetLocation = new PacketLocation();
                er.BaseStream.Position = baseOffset + h.offsetToPacketLocations
                  + (batch.firstPacketLocation + i) * 8;
                packetLocation.size = er.ReadUInt32();
                packetLocation.offset = er.ReadUInt32();

                //read packet's primitives
                Packet dstPacket = dst.packets[i];
                er.BaseStream.Position = baseOffset + h.offsetData + packetLocation.offset;
                dumpPacketPrimitives(attribs, (int)packetLocation.size, er, dstPacket);

                //read matrix data for current packet
                MatrixData matrixData = new MatrixData();
                er.BaseStream.Position = baseOffset + h.offsetToMatrixData
                  + (batch.firstMatrixData + i) * 8;
                matrixData.unknown1 = er.ReadUInt16();
                matrixData.count = er.ReadUInt16();
                matrixData.firstIndex = er.ReadUInt32();

                //read packet's matrix table
                dstPacket.matrixTable.AddRange(new ushort[matrixData.count]);
                er.BaseStream.Position = baseOffset + h.offsetToMatrixTable
                + 2 * matrixData.firstIndex;
                for (int j = 0; j < matrixData.count; ++j)
                    dstPacket.matrixTable[j] = er.ReadUInt16();
            }
        }

        private static void readShp1Header(EndianBinaryReader er, Shp1Header h)
        {
            h.tag = er.ReadString(Encoding.ASCII, 4);
            h.sizeOfSection = er.ReadUInt32();
            h.batchCount = er.ReadUInt16();
            h.pad = er.ReadUInt16();
            h.offsetToBatches = er.ReadUInt32();
            h.offsetUnknown = er.ReadUInt32();
            h.zero = er.ReadUInt32();
            h.offsetToBatchAttribs = er.ReadUInt32();
            h.offsetToMatrixTable = er.ReadUInt32();
            h.offsetData = er.ReadUInt32();
            h.offsetToMatrixData = er.ReadUInt32();
            h.offsetToPacketLocations = er.ReadUInt32();
        }

        private static void readBatch(EndianBinaryReader er, Batch d)
        {
            d.matrixType = er.ReadByte();
            d.unknown2 = er.ReadByte();
            d.packetCount = er.ReadUInt16();
            d.offsetToAttribs = er.ReadUInt16();
            d.firstMatrixData = er.ReadUInt16();
            d.firstPacketLocation = er.ReadUInt16();
            d.unknown3 = er.ReadUInt16();
            d.unknown4 = er.ReadInt32();
            Helper.toFLOAT(d.unknown4);
            for (int i = 0; i < 3; ++i)
            {
                d.bbMin[i] = er.ReadInt32();
                Helper.toFLOAT(d.bbMin[i]);
            }
            for (int j = 0; j < 3; ++j)
            {
                d.bbMax[j] = er.ReadInt32();
                Helper.toFLOAT(d.bbMax[j]);
            }
        }

        public static void dumpShp1(EndianBinaryReader er, Shp1 dst)
        {
            int shp1Offset = (int)er.BaseStream.Position, i;

            Shp1Header h = new Shp1Header();
            readShp1Header(er, h);

            //read batches
            er.BaseStream.Position = h.offsetToBatches + shp1Offset;
            dst.batches.AddRange(new Batch[h.batchCount]);
            for (i = 0; i < h.batchCount; ++i)
            {
                Batch d = new Batch();
                readBatch(er, d);

                Batch dstBatch = dst.batches[i];

                long filePos = er.BaseStream.Position;
                dumpBatch(d, h, er, shp1Offset, dstBatch);
                er.BaseStream.Position = filePos;
            }
        }

    }
}
